package cmm;
import ui.Editor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;
import static java.lang.Character.isDigit;

/**
 * Created by 小兴安岭 on 2017/12/9.
 */
public class MidCodeRun {
    /*store int variable(a list of map,every map contains key && value)*/
    public ArrayList<HashMap<String,Integer>> intParasStack = new ArrayList<>();
    /*store double variable*/
    public ArrayList<HashMap<String,Float>> doubleParasStack = new ArrayList<>();
    /*store  double array var*/
    public ArrayList<HashMap<String,ArrayList<Float>>> doubleArrayParasStack = new ArrayList<>();
    /*store int array var*/
    public ArrayList<HashMap<String,ArrayList<Integer>>> intArrayParasStack = new ArrayList<>();

    private Editor ui = null;

    public MidCodeRun() {

    }

    public MidCodeRun(Editor ui) {
        this.ui = ui;
    }

    public void runMidCode(Vector<MidCode> midCodes)
    {
        pushScope();
        for (int i = 0;i<midCodes.size();i++)
        {
            switch (midCodes.get(i).getOp()){
                case ASSIGN:
                    runAssign(midCodes.get(i));
                    break;
                case DECLAR:
                    runDeclar(midCodes.get(i));
                    break;
                case CREATE:
                    runCreate(midCodes.get(i));
                    break;
                case CLEAR:
                    runClear(midCodes.get(i));
                    break;
                case GET:
                    runGet(midCodes.get(i));
                    break;
                case SET:
                    runSet(midCodes.get(i));
                    break;
                case PLUS:
                    runPlus(midCodes.get(i));
                    break;
                case MINS:
                    runMins(midCodes.get(i));
                    break;
                case MULTI:
                    runMulti(midCodes.get(i));
                    break;
                case DIV:
                    runDiv(midCodes.get(i));
                    break;
                case JUMP:
                    i = runJump(midCodes.get(i));
                    break;
                case JUMP_LOW:
                    i = runJumpLow(midCodes.get(i));
                    break;
                case JUMP_HIGH:
                    i = runJumpHigh(midCodes.get(i));
                    break;
                case JUMP_EQUAL:
                    i = runJumpEqual(midCodes.get(i));
                    break;
                case JUMP_LE:
                    i = runJumpLe(midCodes.get(i));
                    break;
                case JUMP_GE:
                    i = runJumpGe(midCodes.get(i));
                    break;
                case JUMP_NE:
                    i = runJumpNe(midCodes.get(i));
                    break;
                case INPUT:
                    runInput(midCodes.get(i));
                    break;
                case OUTPUT:
                    runOutput(midCodes.get(i));
                    break;
                default:
                    break;
            }
        }
        popScope();
    }
    private void runAssign(MidCode midCode)
    {
        Number arg1 = getValue(midCode.getArg1());
        if(hasIntIDInScope(midCode.getResult()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue());
        }
        else if(hasDoubleIDInScope(midCode.getResult()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue());
        }
        else
        {
            if(hasIntID(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue());
            }
            else if(hasDoubleID(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue());
            }
        }

    }
    private void runDeclar(MidCode midCode)
    {
        if(midCode.getArg1().equals("int"))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),0);
        }
        else if (midCode.getArg1().equals("double"))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),(float)0);
        }
        else if (midCode.getArg1().equals("intArray"))
        {
            ArrayList<Integer> temp= new ArrayList<>();
            for(int i = 0;i<Integer.valueOf(midCode.getArg2());i++)
            {
                temp.add(0);
            }
            getIntArrayParaByID(midCode.getResult()).put(midCode.getResult(), temp);
        }
        else if (midCode.getArg1().equals("doubleArray"))
        {
            ArrayList<Float> temp= new ArrayList<>();
            for(int i = 0;i<Integer.valueOf(midCode.getArg2());i++)
            {
                temp.add((float)0);
            }
            getDoubleArrayParaByID(midCode.getResult()).put(midCode.getResult(), temp);
        }
    }
    private void runCreate(MidCode midCode)
    {
        pushScope();
    }
    private void runClear(MidCode midCode)
    {
        popScope();
    }
    private void runGet(MidCode midCode)
    {
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntArrayIDInScope(midCode.getArg1()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),
                    getIntArrayParaByID(midCode.getArg1()).get(midCode.getArg1()).get(arg2.intValue()));
        }
        else if (hasDoubleArrayIDInScope(midCode.getArg1()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),
                    getDoubleArrayParaByID(midCode.getArg1()).get(midCode.getArg1()).get(arg2.intValue()));
        }
        else
        {
            if(hasIntArrayID(midCode.getArg1()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),
                        getIntArrayParaByID(midCode.getArg1()).get(midCode.getArg1()).get(arg2.intValue()));
            }
            else if (hasDoubleArrayID(midCode.getArg1()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),
                        getDoubleArrayParaByID(midCode.getArg1()).get(midCode.getArg1()).get(arg2.intValue()));
            }
        }
    }
    private void runSet(MidCode midCode)
    {
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntArrayIDInScope(midCode.getArg1()))
        {
            ArrayList<Integer> temp = getIntArrayParaByID(midCode.getArg1()).get(midCode.getArg1());
            temp.set(arg2.intValue(),
                    getIntParaByID(midCode.getResult()).get(midCode.getResult()));
            getIntArrayParaByID(midCode.getArg1()).put(midCode.getArg1(),temp);
        }
        else if (hasDoubleArrayIDInScope(midCode.getArg1()))
        {
            ArrayList<Float> temp = getDoubleArrayParaByID(midCode.getArg1()).get(midCode.getArg1());
            temp.set(arg2.intValue(),
                    getDoubleParaByID(midCode.getResult()).get(midCode.getResult()));
            getDoubleArrayParaByID(midCode.getArg1()).put(midCode.getArg1(),temp);
        }
        else
        {
            if(hasIntArrayID(midCode.getArg1()))
            {
                ArrayList<Integer> temp = getIntArrayParaByID(midCode.getArg1()).get(midCode.getArg1());
                temp.set(arg2.intValue(),
                        getIntParaByID(midCode.getResult()).get(midCode.getResult()));
                getIntArrayParaByID(midCode.getArg1()).put(midCode.getArg1(),temp);
            }
            else if (hasDoubleArrayID(midCode.getArg1()))
            {
                ArrayList<Float> temp = getDoubleArrayParaByID(midCode.getArg1()).get(midCode.getArg1());
                temp.set(arg2.intValue(),
                        getDoubleParaByID(midCode.getResult()).get(midCode.getResult()));
                getDoubleArrayParaByID(midCode.getArg1()).put(midCode.getArg1(),temp);
            }
        }
    }
    private void runPlus(MidCode midCode)
    {
        Number arg1 = getValue(midCode.getArg1());
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntIDInScope(midCode.getResult()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()+arg2.intValue());
        }
        else if(hasDoubleID(midCode.getResult()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()+arg2.floatValue());
        }
        else
        {
            if(hasIntID(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()+arg2.intValue());
            }
            else if(hasDoubleID(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()+arg2.floatValue());
            }
        }
    }
    private void runMins(MidCode midCode)
    {
        Number arg1 = getValue(midCode.getArg1());
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntIDInScope(midCode.getResult()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()-arg2.intValue());
        }
        else if(hasDoubleID(midCode.getResult()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()-arg2.floatValue());
        }
        else
        {
            if(hasIntID(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()-arg2.intValue());
            }
            else if(hasDoubleID(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()-arg2.floatValue());
            }
        }
    }
    private void runMulti(MidCode midCode)
    {
        Number arg1 = getValue(midCode.getArg1());
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntIDInScope(midCode.getResult()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()*arg2.intValue());
        }
        else if(hasDoubleID(midCode.getResult()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()*arg2.floatValue());
        }
        else
        {
            if(hasIntID(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()*arg2.intValue());
            }
            else if(hasDoubleID(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()*arg2.floatValue());
            }
        }
    }
    private void runDiv(MidCode midCode)
    {
        Number arg1 = getValue(midCode.getArg1());
        Number arg2 = getValue(midCode.getArg2());
        if(hasIntIDInScope(midCode.getResult()))
        {
            getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()/arg2.intValue());
        }
        else if(hasDoubleID(midCode.getResult()))
        {
            getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()/arg2.floatValue());
        }
        else
        {
            if(hasIntID(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),arg1.intValue()/arg2.intValue());
            }
            else if(hasDoubleID(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),arg1.floatValue()/arg2.floatValue());
            }
        }
    }
    private int runJump(MidCode midCode)
    {
        return Integer.valueOf(midCode.getResult())-1;
    }
    private int runJumpLow(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1<arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private int runJumpHigh(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1>arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private int runJumpEqual(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1==arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private int runJumpLe(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1<=arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private int runJumpGe(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1>=arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private int runJumpNe(MidCode midCode)
    {
        float arg1 = getValue(midCode.getArg1()).floatValue();
        float arg2 = getValue(midCode.getArg2()).floatValue();
        if(arg1!=arg2)
        {
            return Integer.valueOf(midCode.getResult())-1;
        }
        return midCode.getNumber();
    }
    private void runInput(MidCode midCode)
    {
        if (ui != null) {
            ui.read();
            while(Editor.readString.equals("")){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(hasIntIDInScope(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),Integer.valueOf(Editor.readString));
            }
            else if(hasDoubleIDInScope(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),Float.valueOf(Editor.readString));
            }
            else
            {
                if(hasIntID(midCode.getResult()))
                {
                    getIntParaByID(midCode.getResult()).put(midCode.getResult(),Integer.valueOf(Editor.readString));
                }
                else if(hasDoubleID(midCode.getResult()))
                {
                    getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),Float.valueOf(Editor.readString));
                }
            }
            Editor.readString = "";
        } else {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String in = null;
            try {
                in = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(hasIntIDInScope(midCode.getResult()))
            {
                getIntParaByID(midCode.getResult()).put(midCode.getResult(),Integer.valueOf(in));
            }
            else if(hasDoubleIDInScope(midCode.getResult()))
            {
                getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),Float.valueOf(in));
            }
            else
            {
                if(hasIntID(midCode.getResult()))
                {
                    getIntParaByID(midCode.getResult()).put(midCode.getResult(),Integer.valueOf(in));
                }
                else if(hasDoubleID(midCode.getResult()))
                {
                    getDoubleParaByID(midCode.getResult()).put(midCode.getResult(),Float.valueOf(in));
                }
            }
        }
    }
    private void runOutput(MidCode midCode)
    {
        Number result = getValue(midCode.getResult());
        if(result instanceof Float)
        {
            System.out.println(result.floatValue());
        }
        else
        {
            System.out.println(result.intValue());
        }
    }
    /* add new element into arrayList*/
    private void pushScope(){
        intParasStack.add(new HashMap<>());
        doubleParasStack.add(new HashMap<>());
        intArrayParasStack.add(new HashMap<>());
        doubleArrayParasStack.add(new HashMap<>());

    }
    /*delete the element which added in pushScope*/
    private void popScope(){
        intParasStack.remove(intParasStack.size()-1);
        doubleParasStack.remove(doubleParasStack.size()-1);
        intArrayParasStack.remove(intArrayParasStack.size()-1);
        doubleArrayParasStack.remove(doubleArrayParasStack.size()-1);
    }
    /*to find out if intParasStack contains ID*/
    private boolean hasIntID(String ID){
        for(int i=intParasStack.size()-1;i>=0;i--){
            if(intParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if intParasStack contains ID only in current scope*/
    private boolean hasIntIDInScope(String ID){
        return intParasStack.get(intParasStack.size()-1).containsKey(ID);
    }
    /*to find out if doubleParasStack contains ID*/
    private boolean hasDoubleID(String ID){
        for(int i=doubleParasStack.size()-1;i>=0;i--){
            if(doubleParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if doubleParasStack contains ID only in current scope*/
    private boolean hasDoubleIDInScope(String ID){
        return doubleParasStack.get(doubleParasStack.size()-1).containsKey(ID);
    }
    /*to find out if intArrayParasStack contains ID*/
    private boolean hasIntArrayID(String ID){
        for(int i=intArrayParasStack.size()-1;i>=0;i--){
            if(intArrayParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if intArrayParasStack contains ID only in current scope*/
    private boolean hasIntArrayIDInScope(String ID){
        return intArrayParasStack.get(intArrayParasStack.size()-1).containsKey(ID);
    }
    /*to find out if doubleArrayParasStack contains ID*/
    private boolean hasDoubleArrayID(String ID){
        for(int i=doubleArrayParasStack.size()-1;i>=0;i--){
            if(doubleArrayParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if doubleArrayParasStack contains ID only in current scope*/
    private boolean hasDoubleArrayIDInScope(String ID){
        return doubleArrayParasStack.get(doubleArrayParasStack.size()-1).containsKey(ID);
    }
    /*get hashmap of the int ID*/
    private HashMap<String,Integer> getIntParaByID(String ID){
        for(int i=intParasStack.size()-1;i>=0;i--){
            if(intParasStack.get(i).containsKey(ID)){
                return intParasStack.get(i);
            }
        }
        return intParasStack.get(intParasStack.size()-1);
    }
    /*get hashmap of the double ID*/
    private HashMap<String,Float> getDoubleParaByID(String ID){
        for(int i=doubleParasStack.size()-1;i>=0;i--){
            if(doubleParasStack.get(i).containsKey(ID)){
                return doubleParasStack.get(i);
            }
        }
        return doubleParasStack.get(doubleParasStack.size()-1);
    }
    /*get hashmap of the int array ID*/
    private HashMap<String, ArrayList<Integer>> getIntArrayParaByID(String ID){
        for(int i=intArrayParasStack.size()-1;i>=0;i--){
            if(intArrayParasStack.get(i).containsKey(ID)){
                return intArrayParasStack.get(i);
            }
        }
        return intArrayParasStack.get(intArrayParasStack.size()-1);
    }
    /*get hashmap of the double array ID*/
    private HashMap<String,ArrayList<Float>> getDoubleArrayParaByID(String ID){
        for(int i=doubleArrayParasStack.size()-1;i>=0;i--){
            if(doubleArrayParasStack.get(i).containsKey(ID)){
                return doubleArrayParasStack.get(i);
            }
        }
        return doubleArrayParasStack.get(doubleArrayParasStack.size()-1);
    }
    private Number getValue(String text)
    {
        if(isDigit(text.charAt(0)))
        {
            if(text.contains("."))
            {
                return Float.valueOf(text);
            }
            else
            {
                return Integer.valueOf(text);
            }
        }
        else
        {
            if(hasIntIDInScope(text))
            {
                return getIntParaByID(text).get(text);
            }
            else if(hasDoubleID(text))
            {
                return getDoubleParaByID(text).get(text);
            }
            else
            {
                if(hasIntID(text))
                {
                    return getIntParaByID(text).get(text);
                }
                else if(hasDoubleID(text))
                {
                    return getDoubleParaByID(text).get(text);
                }
            }
        }
        return 0;
    }
}
